package com.elite.security.web;

import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.security.web.SecurityFilterChain;

import java.io.IOException;
import java.util.List;


/**
 * @description: 委托
 * @Author 万伟焕
 * @Date 2024-02-01 19:17:38
 **/
public class CustomDelegateVirtualFilterChain implements FilterChain {
	private static final Log logger = LogFactory.getLog(CustomDelegateVirtualFilterChain.class);

	private final   FilterChain originalChain;

	private volatile RouterSingleFilterChain routerSingleFilterChain;


	public CustomDelegateVirtualFilterChain(FilterChain originalChain, RouterSingleFilterChain routerSingleFilterChain) {
		this.originalChain = originalChain;
		this.routerSingleFilterChain = routerSingleFilterChain;
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {

		// 获取过滤器链
		List<SecurityFilterChain> currentChain = routerSingleFilterChain.getSingleFilterChain().getFilterChains();

		// 获取过滤器执行
		List<Filter> executeFilters = getFilters(currentChain, request);
		// 过滤器链为null，证明这个请求是 忽略的，不用认证授权请求
		if (executeFilters == null || executeFilters.size() == 0) {
			originalChain.doFilter(request, response);
			return;
		}
		//创建一条虚拟的过滤链
		FilterChain filterChain = new CustomVirtualFilterChain(originalChain, this, executeFilters, currentChain, routerSingleFilterChain);
		//调用（核心）
		filterChain.doFilter(request, response);

	}

	private List<Filter> getFilters(List<SecurityFilterChain> filterChains, ServletRequest request) {

		int count = 0;
		for (SecurityFilterChain chain : filterChains) {
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.format("Trying to match request against %s (%d/%d)", chain, ++count, filterChains.size()));
			}
			//路径匹配（核心）
			if (chain.matches((HttpServletRequest) request)) {
				return chain.getFilters();
			}
		}
		return null;
	}


	private static final class CustomVirtualFilterChain implements FilterChain {

		private static final Log logger = LogFactory.getLog(CustomVirtualFilterChain.class);
		private final List<Filter> additionalFilters;

		private List<SecurityFilterChain> currentChain;
		private RouterSingleFilterChain routerSingleFilterChain;
		private FilterChain originalChain;
		private CustomDelegateVirtualFilterChain delegateChain;
		private final int size;

		private int currentPosition = 0;

		private CustomVirtualFilterChain(FilterChain originalChain, CustomDelegateVirtualFilterChain delegateChain, List<Filter> additionalFilters, List<SecurityFilterChain> currentChain, RouterSingleFilterChain routerSingleFilterChain) {
			this.additionalFilters = additionalFilters;
			this.size = additionalFilters.size();
			this.originalChain = originalChain;
			this.currentChain = currentChain;
			this.routerSingleFilterChain = routerSingleFilterChain;
			this.delegateChain = delegateChain;
		}

		@Override
		public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {

			if (this.currentPosition == this.size) {
				/**
				 * 该逻辑是解决业务：
				 * 业务场景： 现在设计一个权限系统通过页面来维护整个项目权限。现在有业务有个A项目。现在通过页面来修改A项目的接口访问权限，如：现在有个/hello接口本来访问权限是vip1。
				 * 			现在修改成vip2可以访问。现在问题来了:数据库已经修改成vip2了，但是A项目的过滤器链还是vip1，所以用户有 vip1 权限的还是可以访问 /hello 接口的。
				 *
				 * 排除方案：项目重启，因为在生产中每次权限修改还要去项目重启的。
				 * 解决问题：重新构建一条过滤器链
				 *
				 * 解决过程中遇到问题：
				 * 		1. 过滤器链并发修改问题：目前使用 lock 锁解决。
				 * 		2. 刚好有个请求进来并且该请求刚好也是本次修改的，那么就会出现一个问题：该请求此时执行到了安全过滤器链中，还未退出安全过滤器链，这时因为页面修改导致更新了A项目安全过滤器链
				 * 		   虽然安全过滤器链更新了，但是该请求还在旧的安全过滤器链中。等它退出安全过滤器链它就会执行到 Controller 哪里。这就导致本来该请求是没有访问权限的，结果被它访问了！
				 * 		   虽然这个业务场景触发几率很小，并且该后果是可以接受的，无非就是多被它多访问一次（看公司接不接受！）。但是对应程序员（我）来说，有点接受不了，权限管理无非讲究是安全两个字！
				 * 		   又有多少系统是因为安全没有做好导致公司收到财产损失的！
				 *
				 *
				 * 	解决上面问题重点对象在 routerSingleFilterChain 身上
				 * 该下面逻辑就是解决这个问题的，
				 */
				List<SecurityFilterChain> nextChain = routerSingleFilterChain.getSingleFilterChain().getFilterChains();

				if (currentChain == nextChain) {
					// 证明过滤器链没有改变，直接向下执行
					originalChain.doFilter(request, response);
				} else {
					// 证明过滤器改变了，再执行一次新的安全过滤器链
					delegateChain.doFilter(request,response);
				}
				return;
			}
			this.currentPosition++;
			Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
			if (logger.isTraceEnabled()) {
				String name = nextFilter.getClass().getSimpleName();
				logger.trace(LogMessage.format("Invoking %s (%d/%d)", name, this.currentPosition, this.size));
			}
			nextFilter.doFilter(request, response, this);
		}

	}
}
